package com.g0dkar.leet.util.ognl.typeConversion.primitive;

import java.sql.Time;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import com.g0dkar.leet.util.ognl.typeConversion.LeetTypeConverter;
import com.g0dkar.leet.util.ognl.typeConversion.TypeConverter;

/**
 * Executes conversions with {@link Date}, {@link Calendar},
 * {@link java.sql.Date SQL Dates}, {@link Time SQL Times} and {@link Timestamp
 * SQL Timestamps}.
 * 
 * @author Rafael g0dkar Lins
 * 
 */
@LeetTypeConverter
public class DateConverter implements TypeConverter {
    private static final DateFormat DATE_FORMATTER = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.FULL);

    public String toString(Object value, Class<?>... genericType) throws Exception {
        if (Date.class.isAssignableFrom(value.getClass())) {
            return new NumberConverter().toString(((Date) value).getTime());
        }

        if (Calendar.class.isAssignableFrom(value.getClass())) {
            return new NumberConverter().toString(((Calendar) value).getTimeInMillis());
        }

        return null;
    }

    public Object fromString(String value, Class<?> toType, Class<?>... genericTypes) throws Exception {
        // Date
        if (toType.equals(Date.class)) {
            if (isNumericDate(value)) {
                Long numericDate = (Long) new NumberConverter().fromString(value, Long.class);
                return new Date(numericDate);
            } else {
                return parse(value);
            }
        }

        // Calendar
        if (toType.isAssignableFrom(Calendar.class)) {
            Calendar calendar = Calendar.getInstance();

            if (isNumericDate(value)) {
                Long numericDate = (Long) new NumberConverter().fromString(value, Long.class);
                calendar.setTimeInMillis(numericDate);
            } else {
                calendar.setTime(parse(value));
            }

            return calendar;
        }

        // SQL Date
        if (toType.equals(java.sql.Date.class)) {
            if (isNumericDate(value)) {
                return new java.sql.Date((Long) new NumberConverter().fromString(value, Long.class));
            } else {
                return java.sql.Date.valueOf(value);
            }
        }

        // SQL Time
        if (toType.equals(Time.class)) {
            if (isNumericDate(value)) {
                return new Time((Long) new NumberConverter().fromString(value, Long.class));
            } else {
                return Time.valueOf(value);
            }
        }

        // SQL Timestamp
        if (toType.equals(Timestamp.class)) {
            if (isNumericDate(value)) {
                return new Timestamp((Long) new NumberConverter().fromString(value, Long.class));
            } else {
                return Timestamp.valueOf(value);
            }
        }

        return null;
    }

    public boolean isNumericDate(String value) {
        return value.matches("\\d+");
    }

    public boolean canConvert(Class<?> toType) {
        return toType.equals(Date.class) || toType.equals(Calendar.class) || toType.equals(java.sql.Date.class)
                || toType.equals(Time.class) || toType.equals(Timestamp.class);
    }

    public Object newInstance(Class<?> toType) throws Exception {
        return fromString(String.valueOf(System.currentTimeMillis()), toType);
    }

    public String toDisplayableString(Object value, Class<?>... genericTypes) throws Exception {
        if (canConvert(value.getClass())) {
            SimpleDateFormat dateFormatter = new SimpleDateFormat();

            if (Date.class.isAssignableFrom(value.getClass())) {
                return dateFormatter.format(new Date(((Date) value).getTime()));
            }

            if (Calendar.class.isAssignableFrom(value.getClass())) {
                return dateFormatter.format(new Date(((Calendar) value).getTimeInMillis()));
            }
        }

        return null;
    }

    private synchronized Date parse(String value) throws ParseException {
        return DATE_FORMATTER.parse(value);
    }
}
